RISC-V A-extension
RISC-V的A-extension指的是atomic instruction extension,而atomic instruction指的是要確保該指令是不會被中斷的,也就是說可能要確保在該址令執行前的指令皆完成,又或者是該指令執行完之後才能執行額外的指令。主要目的為在多核心的系統中能夠確保資料的一致性。
而A-extension內分成兩大類,一類為LR/SC指令,另一類為AMO指令。
LR/SC指令指的是Load-Reserved/Store-Conditional,透過LR去標記特定的位址(Reserved),而在SC時會檢查該位址是否有被Reserved,如果沒有則判斷為錯誤,而只要有SC指令發生就會將原先的所有Reserve清空,來確保一次只會有一個SC發生。 (簡單來說,如果同時有兩個SC指令在不同顆core上發生,則core0碰完該位址後core1會store失敗。)
AMO指令則是可以針對記憶體位置進行運算,而只需要一道指令。在有AMO指令之前,我們如果要對一個記憶體位址做運算,會有以下程式碼。
//讀一個存在a1的值,寫到a0,同時做ori 0x1111,最後再寫回a1
lui a2 0x1111
lw a0 0(a1)
or a0 a0 a2
sw a0 0(a1)
而由於這是三道指令組成,指令間可能有中斷發生,又或者是在寫回前有人想要去碰a1這個位置,導致會有行為與預期不符的狀況。
而在amo指令下,我們可以直接這樣寫。
lui a2 0x1111
amoor a0 a1 a2
在我們讀取mem[reg[a1]],做or,寫回mem[reg[a1]]的這段時間,這個位址的值是不會改變的。
LR/SC測試
我們測試LR/SC功能,SC分為success及fail
TEST(ISATESTSuiteLRSC, LR_W)
{
ALISS::memory = (uint8_t *)std::malloc(4 * 1024 * 1024); //4MB size for test
uint64_t* memory64 = (uint64_t*)ALISS::memory;
memory64[0x40000 / 8] = 0xffffffff88888888;
ALISS::reg[10] = 0x0;
ALISS::reg[11] = 0x40000;
ALISS::reservation = false;
uint32_t insn = 0x1005a52f; // lr a0, a1
ALISS::ID_EX_WB(insn);
EXPECT_EQ(ALISS::reg[10], -2004318072 ); //load -2004318072;
EXPECT_EQ(ALISS::reservation, true);
free(ALISS::memory);
}
TEST(ISATESTSuiteLRSC, SC_W_FAIL)
{
ALISS::memory = (uint8_t *)std::malloc(4 * 1024 * 1024); //4MB size for test
uint64_t* memory64 = (uint64_t*)ALISS::memory;
ALISS::reg[10] = 0x0;
ALISS::reg[11] = 0xaaaa;
ALISS::reg[12] = 0x40000;
ALISS::reservation = false;
uint32_t insn = 0x18b6252f; // sc a0, a1, a2
ALISS::ID_EX_WB(insn);
EXPECT_EQ(memory64[0x40000 / 8], 0 ); //0 // failure
EXPECT_EQ(ALISS::reg[10], 1 ); //1 //failure
free(ALISS::memory);
}
TEST(ISATESTSuiteLRSC, SC_W_SUCCESS)
{
ALISS::memory = (uint8_t *)std::malloc(4 * 1024 * 1024); //4MB size for test
uint64_t* memory64 = (uint64_t*)ALISS::memory;
ALISS::reg[10] = 0x0;
ALISS::reg[11] = 0xaaaa;
ALISS::reg[12] = 0x40000;
ALISS::reservation = true;
uint32_t insn = 0x18b6252f; // sc a0, a1, a2
ALISS::ID_EX_WB(insn);
EXPECT_EQ(memory64[0x40000 / 8], 0xaaaa );
EXPECT_EQ(ALISS::reg[10], 0 ); //0 //success
free(ALISS::memory);
}
LR/SC實作
LR/SC的實作可以參考LW/SW,差別只在於沒有IMM,以及要對reservation做判斷而已。
case 0x2: //LR.W
{
reg[rd] = sext(get_mem_w(reg[rs1]),32);
reservation = true;
break;
}
case 0x3: //SC.W
{
if(reservation)
{
set_mem_w(reg[rs1], (uint32_t)reg[rs2]);
reg[rd] = 0;
}
else
{
reg[rd] = 1;
}
reservation = false;
break;
}
AMO實作
AMO實作上就是結合Load/Store,並將要Store回去的值進行運算,實現結果如下。
case 0x1: //AMOSWP.D
{
reg[rd] = get_mem_d(reg[rs1]);
set_mem_d(reg[rs1], reg[rs2]);
break;
}
case 0x0: //AMOADD.D
{
reg[rd] = get_mem_d(reg[rs1]);
set_mem_d(reg[rs1], reg[rs2] + reg[rd]);
break;
}
case 0x4: //AMOXOR.D
{
reg[rd] = get_mem_d(reg[rs1]);
set_mem_d(reg[rs1], reg[rs2] ^ reg[rd]);
break;
}
case 0xc: //AMOAND.D
{
reg[rd] = get_mem_d(reg[rs1]);
set_mem_d(reg[rs1], reg[rs2] & reg[rd]);
break;
}
case 0x8: //AMOOR.D
{
reg[rd] = get_mem_d(reg[rs1]);
set_mem_d(reg[rs1], reg[rs2] | reg[rd]);
break;
}
case 0x10: //AMOMIN.D
{
reg[rd] = get_mem_d(reg[rs1]);
set_mem_d(reg[rs1], (int64_t)reg[rs2] < (int64_t)reg[rd] ? reg[rs2] : reg[rd] );
break;
}
case 0x14: //AMOMAX.D
{
reg[rd] = get_mem_d(reg[rs1]);
set_mem_d(reg[rs1], (int64_t)reg[rs2] > (int64_t)reg[rd] ? reg[rs2] : reg[rd] );
break;
}
case 0x18: //AMOMINU.D
{
reg[rd] = get_mem_d(reg[rs1]);
set_mem_d(reg[rs1], reg[rs2] < reg[rd] ? reg[rs2] : reg[rd] );
break;
}
case 0x1c: //AMOMAXU.D
{
reg[rd] = get_mem_d(reg[rs1]);
set_mem_d(reg[rs1], reg[rs2] > reg[rd] ? reg[rs2] : reg[rd] );
break;
}
以上,A-extension就告一段落,指令數量雖然不少但實現上很接近,因此實作不難。
碎碎念 : 明天進到M-extension,A-extension一些概念之前其實也沒有很清楚,藉這個機會多理解不少。